-- Rogue Rage
--
-- This program is free software: you can redistribute it and/or modify
-- it under the terms of the GNU General Public License as published by
-- the Free Software Foundation, either version 3 of the License, or
-- (at your option) any later version.
--
-- This program is distributed in the hope that it will be useful,
-- but WITHOUT ANY WARRANTY; without even the implied warranty of
-- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
-- GNU General Public License for more details.
--
-- You should have received a copy of the GNU General Public License
-- along with this program.  If not, see <http://www.gnu.org/licenses/>.
--
-- Darren Grey
-- http://gruesomegames.com

require "engine.class"
require "engine.GameTurnBased"
--require "engine.GameEnergyBased"
require "engine.interface.GameMusic"
require "engine.interface.GameSound"
require "engine.interface.GameTargeting"
require "engine.KeyBind"
require "engine.World"
local Savefile = require "engine.Savefile"
local DamageType = require "engine.DamageType"
local Tiles = require "engine.Tiles"
local Zone = require "engine.Zone"
local Map = require "engine.Map"
local Level = require "engine.Level"
local Birther = require "engine.Birther"
local Shader = require "engine.Shader"
local HighScores = require "engine.HighScores"

local PlayerDisplay = require "mod.class.PlayerDisplay"
local Grid = require "mod.class.Grid"
local Actor = require "mod.class.Actor"
local Player = require "mod.class.Player"
local NPC = require "mod.class.NPC"

local LogDisplay = require "engine.LogDisplay"
local DebugConsole = require "engine.DebugConsole"
local Tooltip = require "engine.Tooltip"
local FlyingText = require "engine.FlyingText"

local GetQuantity = require "engine.dialogs.GetQuantity"
local Particles = require "engine.Particles"

local Dialog = require "engine.ui.Dialog"
--local StanceMenu = require "mod.dialogs.StanceMenu"
local QuitDialog = require "mod.dialogs.Quit"

--module(..., package.seeall, class.inherit(engine.GameEnergyBased, engine.interface.GameMusic, engine.interface.GameSound, engine.interface.GameTargeting))
module(..., package.seeall, class.inherit(engine.GameTurnBased, engine.interface.GameMusic, engine.interface.GameSound, engine.interface.GameTargeting))

-- Tell the engine that we have a fullscreen shader that supports gamma correction
support_shader_gamma = true

function _M:init()
	engine.GameTurnBased.init(self, engine.KeyBind.new(), 1000, 100)
	--engine.GameEnergyBased.init(self, engine.KeyBind.new(), 600, 100)
	engine.interface.GameMusic.init(self)
	engine.interface.GameSound.init(self)
	
	
	
	-- Pause at birth
	self.paused = true
	self.freeze = false

	-- Same init as when loaded from a savefile
	self:loaded()
end

function _M:run()
	self.logdisplay = LogDisplay.new(290, self.h - 105, self.w - 695, 99, nil, nil, nil, {255,255,255}, {30,30,30})
	self.player_display = PlayerDisplay.new()

	self.tooltip = Tooltip.new(nil, nil, {235,235,235}, {30,30,30,230}, 450)

	self.dramatic_pause = 0

	--self.flyers = FlyingText.new()

	local flysize = 32
	self.flyers = FlyingText.new("/data/font/VeraMono.ttf", flysize, "/data/font/VeraMono.ttf", flysize + 3)
	self.flyers:enableShadow(0.6)
	self:setFlyingText(self.flyers)

	self.log = function(...) self.logdisplay(...) end
    --self.logSeen = function(e, ...) if e and self.level.map.seens(e.x, e.y) then self.log(...) end end
	self.logPlayer = function(e, ...) if e == self.player then self.log(...) end end

	-- Run the current music if any
	self:playMusic("sabredance.ogg")
	
	-- Setup inputs
	self:setupCommands()
	self:setupMouse()

	config.settings.mouse_cursor = false
	game:updateMouseCursor()
	game:saveSettings("mouse_cursor", ("mouse_cursor = %s\n"):format(tostring(config.settings.mouse_cursor)))

	-- Starting from here we create a new game
	if not self.player then self:newGame() end

	-- Setup the targetting system
	engine.interface.GameTargeting.init(self)

	-- Ok everything is good to go, activate the game in the engine!
	self:setCurrent()

	if self.level then self:setupDisplayMode() end
end

function _M:newGame()
	self.player = Player.new{name=self.player_name, game_ender=true}
	Map:setViewerActor(self.player)
	self:setupDisplayMode()
	
	if not world.best_score then
		world.best_score = 0
		world:saveWorld()
	end

	self.always_target = "health"
	self.creating_player = true
	local birth = Birther.new(nil, self.player, {"base", "role" }, function()
		self:changeLevel(1, "jam")
		self.player.life = self.player.max_life
		print("[PLAYER BIRTH] resolve...")
		self.player:resolve()
		self.player:resolve(nil, true)
		self.player.energy.value = self.energy_to_act
		self.paused = true
		self.creating_player = false
		print("[PLAYER BIRTH] resolved!")
	end)
	self:registerDialog(birth)
end

function _M:loaded()
	engine.GameTurnBased.loaded(self)
	--engine.GameEnergyBased.loaded(self)
	engine.interface.GameMusic.loaded(self)
	engine.interface.GameSound.loaded(self)
	Zone:setup{npc_class="mod.class.NPC", grid_class="mod.class.Grid", object_class="mod.class.Object"}
	Map:setViewerActor(self.player)
	self:setupDisplayMode(false, "init")
	self:setupDisplayMode(false, "postinit")
	if self.player then self.player.changed = true end
	--[[local th, tw = 64, 64
	Map:setViewPort(0, 0, self.w, self.h, tw, th, nil, 54, true)--]]
	self.key = engine.KeyBind.new()
end

function _M:setupDisplayMode()
	--print("[DISPLAY MODE] 32x32 ASCII/background")
	--local th = 48
	--local tw = math.floor(math.sqrt(0.75) * (th + 0.5))
	
	if not mode or mode == "init" then
		Map:resetTiles()
	end
	
	if not mode or mode == "postinit" then
		Tiles.prefix = "/data/gfx/"
	
		local th, tw = 64, 64
		local pot_th = math.pow(2, math.ceil(math.log(th-0.1) / math.log(2.0)))
		local fsize = math.floor( pot_th/th*(0.7 * th + 5) )
		
		Map:setViewPort(0, 0, self.w, self.h, tw, th, nil, fsize, false)
		Map.tiles.use_images = true
		--Map.tiles.nicer_tiles = true

		if self.level then
			self.level.map:recreate()
			engine.interface.GameTargeting.init(self)
			self.level.map:moveViewSurround(self.player.x, self.player.y, 8, 8)
		end
	
		-- Create the framebuffer
		self.fbo = core.display.newFBO(Map.viewport.width, Map.viewport.height)
		if self.fbo then self.fbo_shader = Shader.new("main_fbo") if not self.fbo_shader.shad then self.fbo = nil self.fbo_shader = nil end end
		if self.player then self.player:updateMainShader() end
	end
end

-- System mouse cursor)
function _M:setMouseCursor()
   core.display.setMouseCursor(0, 0, nil, nil)
end

function _M:updateMouseCursor()
   core.display.setMouseCursor(0, 0, nil, nil)
end

function _M:save()
	return class.save(self, self:defaultSavedFields{}, true)
end

function _M:getSaveDescription()
	return {
		name = self.player.name,
		description = ([[Jamming %s.]]):format(self.zone.name),
	}
end

function _M:leaveLevel(level, lev, old_lev)
	if level:hasEntity(self.player) then
		level.exited = level.exited or {}
		if lev > old_lev then
			level.exited.down = {x=self.player.x, y=self.player.y}
		else
			level.exited.up = {x=self.player.x, y=self.player.y}
		end
		level.last_turn = game.turn
		level:removeEntity(self.player)
	end
end

function _M:changeLevel(lev, zone)
	if self.zone and self.zone.wild then
		self.player.wild_x = self.player.x
		self.player.wild_y = self.player.y
	end

	local old_lev = (self.level and not zone) and self.level.level or -1000
	if zone then
		if self.zone then
			self.zone:leaveLevel(false, lev, old_lev)
			self.zone:leave()
		end
		if type(zone) == "string" then
			self.zone = Zone.new(zone)
		else
			self.zone = zone
		end
	end
	self.zone:getLevel(self, lev, old_lev)

	if self.zone.wild then
		self.player:move(self.player.wild_x, self.player.wild_y, true)
	elseif lev > old_lev then
		self.player:move(self.level.default_up.x, self.level.default_up.y, true)
	else
		self.player:move(self.level.default_down.x, self.level.default_down.y, true)
	end
	self.level:addEntity(self.player)

	if not self.zone.no_regen then
		local x, y = util.findFreeGrid(self.player.x, self.player.y, 1, true, {[Map.ACTOR]=true})
		game.level.map:remove(self.player.x, self.player.y, Map.ACTOR)
		game.level.map(x, y, Map.ACTOR, self.player)
		self.player.x, self.player.y = x, y
	end

	self.changed = true

    if self.zone.on_enter then
       self.zone.on_enter(lev, old_lev, zone)
    end
end

function _M:getPlayer()
	return self.player
end

function _M:setPlayerName(name)
	if not name:find("^random_") then name = "random_"..util.uuid() end
	self.save_name = name
	self.player_name = name
end

--- Says if this savefile is usable or not
function _M:isLoadable()
	return not self:getPlayer(true).dead
end

function _M:tick()
	if self.dramatic_pause > 0 then return end
	--if self.freeze then return end

	if self.level then
		self:targetOnTick()

		--engine.GameTurnBased.tick(self)
		
		--engine.GameEnergyBased.tick(self)
		
		-- Fun stuff: this can make the game realtime, although calling it in display() will make it work better
		-- (since display is on a set FPS while tick() ticks as much as possible
		-- engine.GameEnergyBased.tick(self)
	end
	-- When paused (waiting for player input) we return true: this means we wont be called again until an event wakes us
	if self.paused and not savefile_pipe.saving then return true end
end

--- Called every game turns
-- Does nothing, you can override it
function _M:onTurn()
	-- The following happens only every 10 game turns (once for every turn of 1 mod speed actors)
	if self.turn % 10 ~= 0 then return end
	
	game.player.crit = game.player.crit + 1

	-- Process overlay effects
	self.level.map:processEffects()
end

function _M:display(nb_keyframe)
	if self.dramatic_pause > 0 then
		self.dramatic_pause = self.dramatic_pause - nb_keyframe
		if self.dramatic_pause <= 0 and game.dramatic_pause_co then
			local co = game.dramatic_pause_co
			game.dramatic_pause_co = nil
			coroutine.resume(co)
		end
	end
	if self.dramatic_pause < 0 then self.dramatic_pause = 0 end

	-- If switching resolution, blank everything but the dialog
	if self.change_res_dialog then engine.GameTurnBased.display(self, nb_keyframe) return end

	--if self.change_res_dialog then engine.GameEnergyBased.display(self, nb_keyframe) return end
	
	-- Now the map, if any
	if self.level and self.level.map and self.level.map.finished then
		local map = self.level.map

		-- Display the map and compute FOV for the player if needed
		if self.level.map.changed then
			self.player:playerFOV()
		end


		-- Display using Framebuffer, so that we can use shaders and all
		if self.fbo then
			self.fbo:use(true)
				if self.level.data.background then self.level.data.background(self.level, 0, 0, nb_keyframes) end
				--map:display(0, 0, nb_keyframe)
				map:display(0, 0, nb_keyframe, true)
				map._map:drawSeensTexture(0, 0, nb_keyframe)
			self.fbo:use(false, self.full_fbo)

			self.fbo:toScreen(0, 0, self.w, self.h, self.fbo_shader.shad)


		-- Basic display; no FBOs
		else
			if self.level.data.background then self.level.data.background(self.level, map.display_x, map.display_y, nb_keyframes) end
			--self.level.map:display(nil, nil, nb_keyframe)
			map:display(nil, nil, nb_keyframe, true)
			map._map:drawSeensTexture(map.display_x, map.display_y, nb_keyframe)
		end


		self.player_display:toScreen()

		-- Display the targetting system if active
		self.target:display()

	end

	-- We display the player's interface
	-- self.logdisplay:toScreen()

	if self.player then self.player.changed = false end

	engine.GameTurnBased.display(self, nb_keyframe)
	if self.freeze == false then engine.GameEnergyBased.tick(self) end
	--engine.GameEnergyBased.tick(self)
	--engine.GameEnergyBased.display(self, nb_keyframe)
	
	-- Tooltip is displayed over all else
	local mx, my, button = core.mouse.get()
	--if self.tooltip.w and mx > self.w - self.tooltip.w and my > self.h - self.tooltip.h then
	   self:targetDisplayTooltip(mx+16, my+16)
	--else
	   --self:targetDisplayTooltip(self.w, self.h)
	--end


end

--- Setup the keybinds
function _M:setupCommands()
	-- Make targeting work
	self.normal_key = self.key
	self:targetSetupKey()

	-- One key handled for normal function
	self.key:unicodeInput(true)
	self.key:addBinds
	{
		-- Movements
		MOVE_LEFT = function() self.player:moveDir(4) end,
		MOVE_RIGHT = function() self.player:moveDir(6) end,
		MOVE_UP = function() self.player:moveDir(8) end,
		MOVE_DOWN = function() self.player:moveDir(2) end,
		MOVE_LEFT_UP = function() self.player:moveDir(7) end,
		MOVE_LEFT_DOWN = function() self.player:moveDir(1) end,
		MOVE_RIGHT_UP = function() self.player:moveDir(9) end,
		MOVE_RIGHT_DOWN = function() self.player:moveDir(3) end,

		MOVE_STAY = function()
			self.player:useEnergy()
		end,

		-- Heal potion
		--[[HEAL = function()
			if self.player.no_potions > 0 then
				self.player.life = self.player.max_life
				self.player.no_potions = self.player.no_potions - 1
				self.player.crit = 0
				game.player.changed = true
			end
		end,--]]

		PAUSE = function()
			if game.freeze then game.freeze = false else game.freeze = true end
			game.player.changed = true
		end,
		
		SAVE_GAME = function()
			self:saveGame()
		end,

		-- Exit the game
		QUIT_GAME = function()
			self:onQuit()
		end,

		--SCREENSHOT = function() self:saveScreenshot() end,

		EXIT = function()
			game.freeze = true
			local menu menu = require("engine.dialogs.GameMenu").new{
				"resume",
				"keybinds",
				"video",
				"sound",
				--{"Game Help", function() self:unregisterDialog(menu) self:registerDialog(require("mod.dialogs.GameHelp").new()) end},
				"save",
				"quit"
			}
			self:registerDialog(menu)
		end,

		-- Lua console, you probably want to disable it for releases
		--LUA_CONSOLE = function()
		--	self:registerDialog(DebugConsole.new())
		--end,

	}
	self.key:setCurrent()
end

function _M:setupMouse(reset)
	if reset then self.mouse:reset() end
	self.mouse:registerZone(Map.display_x, Map.display_y, Map.viewport.width, Map.viewport.height, function(button, mx, my, xrel, yrel, bx, by, event)
		-- Handle targeting
		if self:targetMouse(button, mx, my, xrel, yrel, event) then return end

		-- Handle Use menu
		if button == "right" and event == "button" then
			-- Handle Use menu
			self:mouseRightClick(mx, my)
			return
		end

		-- Handle the mouse movement/scrolling
		self.player:mouseHandleDefault(self.key, self.key == self.normal_key, button, mx, my, xrel, yrel, event)
	end)
	--[[ Scroll message log
	self.mouse:registerZone(self.logdisplay.display_x, self.logdisplay.display_y, self.w, self.h, function(button)
		if button == "wheelup" then self.logdisplay:scrollUp(1) end
		if button == "wheeldown" then self.logdisplay:scrollUp(-1) end
	end, {button=true})--]]

	-- Tooltip over the player pane
	self.mouse:registerZone(self.player_display.display_x, self.player_display.display_y, self.player_display.w, self.player_display.h, function(button, mx, my, xrel, yrel, bx, by, event)
		self.player_display.mouse:delegate(button, mx, my, xrel, yrel, bx, by, event)
	end)

	self.mouse:setCurrent()
end

--- Right mouse click on the map
function _M:mouseRightClick(mx, my)
	if game.freeze then game.freeze = false else game.freeze = true end
	game.player.changed = true
end

--- Ask if we really want to close, if so, save the game first
function _M:onQuit()
	self.player:restStop()

	if not self.quit_dialog then
		self.quit_dialog = QuitDialog.new()
		self:registerDialog(self.quit_dialog)
	end
end

--- Requests the game to save
function _M:saveGame()
	-- savefile_pipe is created as a global by the engine
	savefile_pipe:push(self.save_name, "game", self)
	-- self.log("Saving game...")
end
